<?php
/**
 * @author qingyu8@staff.weibo.com
 * @date 2018/04/16
 */
namespace console\controllers;

use common\logic\DiveBooker;
use common\models\Product;
use common\models\Tour;
use common\models\Base;
use common\models\Boat;
use common\models\Booker;
use common\models\BookerItem;
use common\models\Cabin;
use common\models\Occupancy;
use common\models\TableField;
use common\models\Trip;
use common\tool\Alarm;
use common\tool\Oss;
use yii;
use yii\db\Query;
use yii\console\Controller;
use GuzzleHttp;
use Symfony\Component\DomCrawler\Crawler;
use GuzzleHttp\Exception\RequestException;

set_time_limit(0);

class BookerController extends Controller {
    const DEBUG= 0;
    public $type;
    public $singleId;
    public $tripId;
    const HOST = 'https://divebooker.com';
    const API = [
        'boatList' => '/restapi/boatsearch',
        'boatDetail' => '/apiv1/liveaboards/id/%d',
        //boatId
        'trips' => '/restapi/trips/%d',
        //boatId
        'trip' => '/apiv1/liveaboards/id/%d/trip',
        //boatId
        'products' => '/apiv1/liveaboards/id/%d/products',
        //boatId
        'tour' => '/apiv1/liveaboards/id/%d/tour',
        //boatId
        'product' => '/apiv1/liveaboards/id/%d/product',
        //boatId
        'booking'=>'/boatorder/booking/?tripId=%d',//tripId
    ];
    const COOKIE = '__utmz=229250441.1514174811.1.1.utmcsr=mail.qq.com|utmccn=(referral)|utmcmd=referral|utmcct=/; _ym_uid=1514174813187987055; wooTracker=O8cerf54zl6y; __utma=229250441.1057606565.1514174811.1516105821.1516107790.3; _ga=GA1.2.1057606565.1514174811; ei=jqyjacky@gmail.com; PHPSESSID=da8hls1sbl20d47thletmgtiu3; SnapABugHistory=3#; tempDate=true; _gid=GA1.2.1571033334.1523803905; s-start=2018-04-18; s-end=2018-04-21; _ym_isad=2; SnapABugRef=https%3A%2F%2Fdivebooker.com%2Fboatsearch%20https%3A%2F%2Fdivebooker.com%2F; _ym_visorc_24907346=w; _gat_UA-48585200-1=1; SnapABugVisit=22#1523681856';
    public $baseHeader = [
        'Accept' => '*/*',
        'Accept-Language' => 'zh',
        'Accept-Encoding' => 'gzip, deflate',
        'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
        'Cookie' => self::COOKIE,
        'Host' => 'divebooker.com',
        'Connection' => 'keep-alive',
    ];
    const ALARM_MSG_INTERVAL = 600;//相同消息报警频率 s
    const ALARM_TYPE_INTERVAL = 300;//相同类别报警频率 s
    public $alarmMsg = [];// msg=>timestamp,
    public $alarmType = [];// type=>timestamp,
    /**
     * @var GuzzleHttp\Client
     */
    public $guzzleClient;

    public function options($actionID) {
        $params = [
            'type','tripId','singleId',
        ];
        return array_merge($params, parent::options($actionID));
    }

    public function beforeAction($action) {
        $this->guzzleClient = new GuzzleHttp\Client();
        return parent::beforeAction($action);
    }

    public function actionField() {
        TableField::generate();
    }

    public function actionCabintotal() {
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " start\n";
        $offset = 0;
        while (true) {
            $where = (new Query())->select('id')->from('dive_boat')->orderBy('id');
            $list = $where->offset($offset)->limit(20)->all();
            if (empty($list)) {
                echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " done\n";
                break;
            }
            foreach ($list as $v) {
                $total = Cabin::getScalar("boat_id={$v['id']}", 'sum(cabin_number)');
                Boat::update(['cabin_total' => $total], "id={$v['id']}");
            }
            $offset += 20;
        }
    }

    public function actionTripCheck() {
        $boats = Boat::getList('', 'id,booker_url');
        $boats = array_combine(array_column($boats, 'id'), array_column($boats, 'booker_url'));
        $ids = array_keys($boats);
        $now = date('Y-m-d H:i:s');
        foreach ($ids as $id) {
            $trips = Trip::getList("boat_id=$id AND start_time > '$now' AND booker_sync<>'" . Booker::SYNC_DELETED . "'", '*', 'start_time');
            $lastStartTime = null;
            for ($i = 0; $i < count($trips); $i++) {
                $trip = $trips[$i];
                if (!is_null($lastStartTime)) {
                    if ($lastStartTime > $trip['start_time']) {
                        $bookerTripId = Booker::dive2booker($trip['id'], Booker::TYPE_TRIP);
                        echo "boat:$id, booker_url:{$boats[$id]} trip {$trips[$i-1]['id']},{$trip['id']}, BookerTripId:$bookerTripId\n";
                        if ($trip['booker_sync'] == Booker::SYNC_404) Trip::update(['booker_sync' => Booker::SYNC_DELETED], "id={$trip['id']}");
                        if ($trips[$i - 1]['booker_sync'] == Booker::SYNC_404) Trip::update(['booker_sync' => Booker::SYNC_DELETED], "id={$trips[$i-1]['id']}");
                        if ($trip['update_time'] > $trips[$i - 1]['update_time']) {
                            Trip::update(['booker_sync' => Booker::SYNC_DELETED], "id={$trips[$i-1]['id']}");
                        } else {
                            Trip::update(['booker_sync' => Booker::SYNC_DELETED], "id={$trip['id']}");
                        }
                    }
                }
                $lastStartTime = $trip['end_time'];
            }
        }
    }

    public function actionIndex() {
        switch ($this->type) {
            case 'boatList':
                $this->boatList();
                break;
            case 'boatDetailAjax':
                $this->boatDetailAjax();
                break;
            case 'boatDetail':
                $this->boatDetail();
                break;
            case 'trips':
                $this->trips();
                break;
            case 'trip':
                $this->trip();
                break;
            case 'booking':
                $this->booking();
                break;
            case 'products':
                $this->products();
                break;
            case 'tour':
                $this->tour();
                break;
            case 'product':
                $this->product();
                break;
            case 'products_revert':
                $this->products_revert();
                break;
            case 'singleTrip':
                $tripId = $this->tripId;
                $oldTrip = Trip::getOne("id=$tripId");
                $this->singleTrip($oldTrip);
                break;
        }
    }

    public function boatList() {
        $p = 1;
        echo "boatList start\n";
        while (true) {
            echo "page $p\n";
            $r = $this->guzzleClient->request('GET', self::HOST . self::API['boatList'], [
                'query' => ['p' => $p],
                'headers' => array_merge($this->baseHeader, [
                    'Referer' => self::HOST,
                    'X-Requested-With' => 'XMLHttpRequest',
                ])
            ]);
            if (empty($r)) {
                echo "empty res p:$p\n";
                break;
            }
            sleep(2);
            $r = json_decode($r->getBody(), true);
            if (empty($r)) {
                echo "res decode fail p:$p\n";
                break;
            }
            if (empty($r['searchObjects']['boats'])) {
                echo "empty boats p:$p\n";
                break;
            }
            foreach ($r['searchObjects']['boats'] as $id => $v) {
                $mediaData = json_encode($v['mediaData']);
                $countries = implode(',', array_column($v['countries'], 'name'));
                $diveBoatId = Booker::booker2dive($id, Booker::TYPE_BOAT);
                $rating = empty($v['reviews']['value']) ? 0 : $v['reviews']['value'];
                $ratingCount = empty($v['reviews']['cnt']) ? 0 : $v['reviews']['cnt'];

                $countriesCN = '';
                if ($v = Boat::getScalar("countries=:en", 'countries_cn', [':en' => $countries])) $countriesCN = $v;

                if (!$diveBoatId) {
                    $t = Yii::$app->db->beginTransaction();
                    try {
                        Boat::insert([
                            'media_data' => $mediaData,
                            'countries' => $countries,
                            'countries_cn' => $countriesCN,
                            'booker_url' => $v['url'],
                            'rating' => $rating,
                            'rating_count' => $ratingCount,
                        ]);
                        $diveBoatId = Yii::$app->db->lastInsertID;
                        Booker::add($diveBoatId, $id, Booker::TYPE_BOAT);
                        $t->commit();
                        echo __FUNCTION__ . "$id - $diveBoatId added\n";
                    } catch (\Exception $e) {
                        echo __FUNCTION__ . $e->getMessage()." $id - $diveBoatId add fail\n";
                        $t->rollBack();
                    }
                }else{
                    Boat::update([
                        'rating' => $rating,
                        'rating_count' => $ratingCount,
                    ], "id=$diveBoatId");
                }
            }
            $p++;
        }
        echo "boatList end\n";
    }

    private function uploadUrl($url) {
        if (!$url) return false;
        $targetUrl = Yii::$app->db->createCommand("SELECT target_url FROM dive_oss_url WHERE raw_url='$url'")->queryScalar();
        //var_dump($targetUrl);
        if (!$targetUrl) {
            $targetUrl = Oss::upload($url);
            Yii::$app->db->createCommand("REPLACE into dive_oss_url (`target_url`,`raw_url`) values ('$targetUrl','$url')")->execute();
        }
        return $targetUrl;
    }

    public function boatDetailAjax() {
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " start\n";
        $offset = 0;
        while (true) {
            $list = Boat::getList("gallery_json='' OR gallery_json is null", 'id,booker_url,countries', 'id',$offset,20);
            if (empty($list)) {
                echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " done\n";
                break;
            }
            foreach ($list as $k => $v) {
                echo "boatId:{$v['id']}\n";
                $r = $this->guzzleClient->request('GET', str_replace('haz', 'hjz', $v['booker_url']), [
                    'headers' => array_merge($this->baseHeader, [
                        'Referer' => $v['booker_url'],
                        'X-Requested-With' => 'XMLHttpRequest',
                    ]),
                ]);
                $res = json_decode((string)$r->getBody(), true);
                $policy = $res['pageData']['boatItem']['header']['policy'][1]['items'] ?? [];
                $gallery = $res['pageData']['boatItem']['header']['gallery'] ?? [];
                $imgs = [];
                foreach ($gallery as $k1 => $v1) {
                    if ($v1['type'] == 'img') {
                        $imgs[] = $this->uploadUrl($v1['url']);
                    }
                }

                $data = [
                    'policy' => json_encode($policy),
                    'gallery_json' => json_encode($imgs),
                    'booker_last_update' => date('Y-m-d H:i:s'),
                ];
                $cn = [];
                if ($enVal = Boat::getScalar("policy=:en", 'policy_cn', [':en' => $data['policy']])) $cn['policy_cn'] = $enVal;
                TableField::filterEdited($data, 'boat');
                Boat::update($data + $cn, "id={$v['id']}");
                sleep(1);
            }
            $offset += 20;
        }
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " end\n";
    }
    public function boatDetail() {
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " start\n";
        $offset = 0;
        $itemTypes = BookerItem::getColumn('', 'DISTINCT `type_en`');
        while ($list = Boat::getList('', 'id,booker_url,countries,cabin_pic', 'id', $offset, 20)) {
            if($this->singleId) $list=Boat::getList("id={$this->singleId}");
            foreach ($list as $k => $v) {
                $data = [];
                $bookerBoatId = Booker::dive2booker($v['id'], Booker::TYPE_BOAT);
                echo "diveBoatId: {$v['id']}, bookerBoatId: $bookerBoatId\n";
                $url =self::HOST . sprintf(self::API['boatDetail'], $bookerBoatId) . '?token=' . DiveBooker::getToken();
                $r = $this->guzzleClient->request('GET', $url,[
                    'debug'=>self::DEBUG,
                ]);
                $res = json_decode($r->getBody(), true);
                if (empty($res['status']) || $res['status'] != 1) {
                    echo "bookerBoatId $bookerBoatId fail:$r\n";
                    continue;
                }
                $data['name_en'] = $res['results']['name'];
                $data['main_image'] = $this->uploadUrl($res['results']['mainImage']);
                $data['logo'] = $res['results']['logo'];
                if (!$v['booker_url']) continue;
                $r = $this->guzzleClient->request('GET', $v['booker_url'], [
                    'headers' => array_merge($this->baseHeader, [
                        'Referer' => self::HOST,
                    ]),
                    'debug'=>self::DEBUG,
                ]);
                sleep(1);
                $crawler = new Crawler((string)$r->getBody());
                $tmpNode = $crawler->filter('.dvb-oc-content.user-content>p')->getNode(0);
                $tmpNode && $data['description'] = $tmpNode->textContent;
                $features = [];
                $subCrawler = $crawler->filter('.bi-a-tc-col');
                foreach ($subCrawler as $node) {
                    /** @var $node \DOMElement */
                    $nCrawler = new Crawler($node);
                    $itemType = $nCrawler->filter('.dvb-oc-title')->getNode(0)->textContent;
                    if (in_array($itemType, $itemTypes)) {
                        $items = [];
                        $nodes = $nCrawler->filter('.bi-sl-item.ico-green-check');
                        foreach ($nodes as $n) {
                            $items[] = $n->textContent;
                        }
                        $features[$itemType] = $items;
                    }
                }

                $subCrawler = $crawler->filter('.bi-services-list>.btp-item');
                $data['feature_json']=json_encode($features);
                foreach ($subCrawler as $node) {
                    $attr = trim($node->firstChild->textContent);
                    if (strcasecmp('Year built', $attr) === 0) {
                        $data['year_built'] = $node->lastChild->textContent;
                    } elseif (strcasecmp('Length', $attr) === 0) {
                        $data['length'] = $node->lastChild->textContent;
                    } elseif (strcasecmp('Width', $attr) === 0) {
                        $data['width'] = $node->lastChild->textContent;
                    } elseif (strcasecmp('Material', $attr) === 0) {
                        $data['material'] = $node->lastChild->textContent;
                    } elseif (strcasecmp('Languages', $attr) === 0) {
                        $data['languages'] = $node->lastChild->textContent;
                    } elseif (strcasecmp('Number of cabins', $attr) === 0) {
                        $data['cabin_total'] = $node->lastChild->textContent;
                    } elseif (strcasecmp('On-board payment methods', $attr) === 0) {
                        $data['onboard_pay_method'] = $node->lastChild->textContent;
                    } elseif (strcasecmp('Nitrox Membrane', $attr) === 0) {
                        $data['nitrox_membrane'] = $node->lastChild->textContent;
                    }
                }
                $subCrawler = $crawler->filter('.bi-os-item.ico-green-check');
                foreach ($subCrawler as $node) {
                    $attr = trim($node->firstChild->textContent);
                    if (strcasecmp('Nitrox', $attr) === 0) {
                        $data['nitrox'] = trim($node->lastChild->textContent);
                    } elseif (strcasecmp('Wifi', $attr) === 0) {
                        $data['wifi'] = trim($node->lastChild->textContent);
                    } elseif (strcasecmp('Diving Equipment', $attr) === 0) {
                        $data['diving_equipment'] = trim($node->lastChild->textContent);
                    } elseif (strcasecmp('Tech Diving Friendly', $attr) === 0) {
                        $data['tech_diving_friendly'] = trim($node->lastChild->textContent);
                    } else {
                        var_dump($attr);
                        var_dump($node->lastChild->textContent);
                    }
                }
                $p = $crawler->filter('.dvb-oc-content.user-content>p');
                if ($p->count()) {
                    $p->getNode(0)->textContent;
                }
                if (!$v['cabin_pic']) {
                    $imgNode = $subCrawler = $crawler->filter('.bi-a-tc-img>img');
                    if ($imgNode->count()) {
                        $data['cabin_pic'] = $this->uploadUrl($imgNode->attr('src'));
                    }
                }
                $cn = [];
                if ($enVal = Boat::getScalar("name_en=:en", 'name_cn', [':en' => $data['name_en']])) $cn['name_cn'] = $enVal;
                if (isset($data['material']) && $enVal = Boat::getScalar("material=:en", 'material_cn', [':en' => $data['material']])) $cn['material_cn'] = $enVal;
                if (isset($data['languages']) && $enVal = Boat::getScalar("languages=:en", 'languages_cn', [':en' => $data['languages']])) $cn['languages_cn'] = $enVal;
                TableField::filterEdited($data, 'boat');
                $data['booker_last_update'] = date('Y-m-d H:i:s');
                Boat::update($data+$cn, "id={$v['id']}");
            }
            if ($this->singleId) break;
            $offset += 20;
        }
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " end\n";
    }

    public function trips() {
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " start\n";
        $offset = 0;
        while ($list = Boat::getList('', '*', 'id', $offset, 20)) {
            if ($this->singleId) $list = Boat::getList('id=' . $this->singleId,'*','id');
            foreach ($list as $k => $v) {
                echo "boatId {$v['id']} start\n";
                $bookerBoatId = Booker::dive2booker($v['id'], Booker::TYPE_BOAT);
                try {
                    $r = $this->guzzleClient->request('GET', self::HOST . sprintf(self::API['trips'], $bookerBoatId), [
                        'query' => ['p' => 1],
                        'headers' => array_merge($this->baseHeader, [
                            'Referer' => self::HOST,
                            'X-Requested-With' => 'XMLHttpRequest',
                        ]),
                        'timeout' => 60,
                    ]);
                } catch (RequestException $e) {
                    echo "boatId:{$v['id']}|RequestException|" . $e->getCode() . "|" . $e->getMessage() . "\n";
                    if(strpos($e->getMessage(), 'cURL error 52') !== false){

                    }else{
                        throw $e;
                    }
                    continue;
                }
                if (empty((string)$r->getBody())) {
                    echo "empty res booker_boat_id:$bookerBoatId\n";
                    continue;
                }
                sleep(2);
                $r = json_decode($r->getBody(), true);
                if (empty($r)) {
                    echo "res decode fail booker_boat_id:$bookerBoatId\n";
                    continue;
                }
                if (empty($r['trips']['list'])) {
                    echo "empty trips booker_boat_id:$bookerBoatId\n";
                    continue;
                }
                foreach ($r['trips']['list'] as $id => $trip) {
                    $data = [
                        'name_en' => $trip['name'],
                        'boat_id' => $v['id'],
                        'start_time' => date('Y-m-d H:i:s', strtotime($trip['departureDate']['m'] . ' ' . $trip['departureDate']['y'])),
                        'end_time' => date('Y-m-d H:i:s', strtotime($trip['arrivalDate']['m'] . ' ' . $trip['arrivalDate']['y'])),
                        //'departure_location' => $trip['departure']['title'],
                        //'arrival_location' => $trip['arrival']['title'],
                        'update_time' => date('Y-m-d H:i:s'),
                        'policy' => json_encode($trip['policy'], JSON_UNESCAPED_UNICODE),
                    ];

                    $bookerTripId = $trip['boatTripId'];
                    $diveTripId = Booker::booker2dive($bookerTripId, Booker::TYPE_TRIP);
                    $oldTrip = [];
                    $diveTripId && $oldTrip = (new Query())->select('id')->from('dive_trip')->where("id=$diveTripId")->one();
                    if (empty($oldTrip)) {
                        $data['name_cn'] = Trip::getScalar("name_en=:name_en AND name_cn!=''", 'name_cn', [':name_en' => $data['name_en']]);
                        Trip::update(['booker_sync' => Booker::SYNC_DELETED], "(start_time<'{$data['start_time']}' AND end_time>'{$data['start_time']}') OR (start_time<'{$data['end_time']}' AND end_time>'{$data['end_time']}')");
                        Trip::insert($data);
                        $diveTripId = Yii::$app->db->getLastInsertID();
                        Booker::add($diveTripId, $bookerTripId, Booker::TYPE_TRIP);
                    } else {
                        TableField::filterEdited($data, 'trip');
                        Trip::update($data, "id=$diveTripId");
                    }
                    //                    continue;//临时

                    $oldProduct = Product::getOne('id=' . $trip['productId'],'id');
                    if (empty($oldProduct)) {
                        Product::insert(['id' => $trip['productId']]);
                    }
                }
                if($this->singleId) break 2;
            }
            $offset += 20;
        }
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " end\n";
    }

    public function trip() {
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " start\n";
        $offset = 0;
        $now=date('Y-m-d 00:00:00');
        while (true) {
            $list = Trip::getList("start_time > '$now' AND booker_sync<>'".Booker::SYNC_404."'", 'id,boat_id', 'id desc', $offset, 20);
            if (empty($list)) {
                echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " done\n";
                break;
            }
            if ($this->singleId) $list = Trip::getList("id={$this->singleId}");
            foreach ($list as $k => $oldTrip) {
                $this->singleTrip($oldTrip);
            }
            if ($this->singleId) break;
            $offset += 20;
        }
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " end\n";
    }
    public function booking(){
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " start\n";
        $date = date('Y-m-d 00:00:00');
        $offset = 0;
        while ($list = Trip::getList("start_time > '$date' AND booker_sync=''", 'id,boat_id', 'id ASC', $offset, 50)) {
            foreach ($list as $k => $oldTrip) {
                $this->singleBooking($oldTrip);
            }
            $offset += 50;
        }
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " end\n";
    }

    public function singleBooking($oldTrip){
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " {$oldTrip['id']} start\n";
        $bookerTripId = Booker::dive2booker($oldTrip['id'], Booker::TYPE_TRIP);
        try {
            $url =self::HOST . sprintf(self::API['booking'], $bookerTripId);
            $rsp = $this->guzzleClient->request('GET', $url, [
                'headers' => array_merge($this->baseHeader, [
                    'Referer' => self::HOST,
                ]),
                'allow_redirects' => false,
                'timeout'=>60,
                'debug'=>self::DEBUG,
            ]);
        } catch (RequestException $e) {
            echo date('Y/m/d H:i:s') . "RequestException|" . $e->getCode() . "|" . $e->getMessage() . "\n";
            if ($response = $e->getResponse()) {
                if ($response->getStatusCode() == 404) {
                    Trip::update(['booker_sync' => Booker::SYNC_404,'update_time' => date('Y-m-d H:i:s')], "id={$oldTrip['id']}");
                } elseif ($response->getStatusCode() == 500) {
                    Trip::update(['booker_sync' => 'SERVER ERROR','update_time' => date('Y-m-d H:i:s')], "id={$oldTrip['id']}");
                }
            } else {
                $this->alarm("RequestException|" . $e->getCode() . "|" . $e->getMessage(), __FUNCTION__);
            }
            return;
        }
        if($rsp->getStatusCode()==302){
            Trip::update(['booker_sync' => 'REDIRECT','update_time' => date('Y-m-d H:i:s')], "id={$oldTrip['id']}");
            return;
        }
        if (empty((string)$rsp->getBody())) {
            echo "empty res trip:$bookerTripId\n";
            return;
        }
        $r = preg_match('/pageData = ({.*});/', (string)$rsp->getBody(), $matches);
        if (!$r) {
            echo "URL: ".self::HOST . sprintf(self::API['booking'], $bookerTripId)."\n";
            echo $rsp->getBody() . "\n";
            $this->alarm("RequestException|booking 抓取异常trip:$bookerTripId,dive_id:{$oldTrip['id']},url:$url", __FUNCTION__);
            return;
        }
        $r = json_decode($matches[1], true);
        //        echo '<?php return ';
        //        var_export($r);
        //        echo ';';
        //        exit;
        sleep(1);
        foreach ($r['cabins']['items'] as $cabin) {
            foreach ($cabin['occupancies']['items'] as $occupancy) {
                if ($last = end($occupancy['types'])) {
                    $left = $last['id'];//最大可选人数
                } else {
                    $left = 0;
                }
                $id = Booker::booker2dive($occupancy['id'], Booker::TYPE_OCCUPANCY);
                if ($id) Occupancy::update(['free_spaces' => $left,'update_time'=>date('Y-m-d H:i:s')], "id=$id");
            }
        }
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " end\n";
    }

    public function singleTrip($oldTrip){
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " {$oldTrip['id']} start\n";
        $bookerBoatId = Booker::dive2booker($oldTrip['boat_id'], Booker::TYPE_BOAT);
        $bookerTripId = Booker::dive2booker($oldTrip['id'], Booker::TYPE_TRIP);
        try {
            $url=self::HOST . sprintf(self::API['trip'], $bookerBoatId);
            $rsp = $this->guzzleClient->request('GET', $url, [
                'query' => [
                    'token' => DiveBooker::getToken(),
                    'tripid' => $bookerTripId
                ],
                'headers' => [
                    'Cookie' => 'currencyId=1;currencyName=USD;currencySymbol=USD',
                    'Accept-Language' => 'en-US,en;q=0.5',
                ],
                'debug'=>self::DEBUG,
                'timeout' => 60,
            ]);
        } catch (RequestException $e) {
            echo "RequestException|" . $e->getCode() . "|" . $e->getMessage() . "\n";
            if ($e->getResponse()) {
                $errRes = json_decode($e->getResponse()->getBody(), true);
                if ($e->getResponse()->getStatusCode() == 400) {
                    if ($errRes['message'] == 'Trip isn\'t found') {
                        Trip::update(['booker_sync' => Booker::SYNC_404], "id={$oldTrip['id']}");
                        return;
                    }
                }
            }
            $this->alarm("RequestException|" . $e->getCode() . "|" . $e->getMessage(), __FUNCTION__);
            return;
        }
        if (empty((string)$rsp->getBody())) {
            echo "empty res booker_id:$bookerBoatId\n";
            return;
        }
        sleep(2);
        $r = json_decode($rsp->getBody(), true);
        if (empty($r['status']) || $r['status'] != 1) {
            echo "res fail booker_id:$bookerBoatId|" . $rsp->getBody() . "\n";
            return;
        }
        foreach ($r['results']['cabins'] as $id => $cabin) {
            $syncCabin = true;
            if ($syncCabin) {
                $data = [
                    'boat_id' => $oldTrip['boat_id'],
                    'name_en' => $cabin['title'],
                    'img' => $this->uploadUrl($cabin['img']) ?: '',
                    'desc' => $cabin['desc'],
                    'deck' => $cabin['props']['deck']['text'] ?? '',
                    'bedding' => $cabin['props']['bedding']['text'] ?? '',
                    //'size'=>$cabin['props']['size']['textClean'] ?? 0,
                    'occupancy' => $cabin['props']['occupancy']['text'] ?? 0,
                    'bathroom' => $cabin['props']['bathroom']['value'] ?? 0,
                    'aircon' => $cabin['props']['aircon']['value'] ?? 0,
                    'cabin_number' => $cabin['props']['numberCabins']['text'] ?? 0,
                ];
                $diveCabinId = Booker::booker2dive($cabin['id'], Booker::TYPE_CABIN);
                $oldCabin = [];
                $diveCabinId && $oldCabin = (new Query())->select('id')->from('dive_cabin')->where("id=$diveCabinId")->one();
                if (empty($oldCabin)) {
                    $data['name_cn'] = Cabin::getScalar("name_en=:name_en AND name_cn!=''", 'name_cn',[':name_en'=>$data['name_en']]);
                    $data['desc_cn'] = Cabin::getScalar("`desc`=:desc AND desc_cn!=''", 'desc_cn', [':desc' => $data['desc']]);
                    $data['bedding_cn'] = Cabin::getScalar("bedding=:bedding AND bedding_cn!=''", 'bedding_cn',[':bedding'=>$data['bedding']]);
                    $data['deck_cn'] = Cabin::getScalar("deck=:deck AND deck_cn!=''", 'deck_cn',[':deck'=>$data['deck']]);
                    Cabin::insert($data);
                    $diveCabinId = Yii::$app->db->getLastInsertID();
                    Booker::add($diveCabinId, $cabin['id'], Booker::TYPE_CABIN);
                } else {
                    TableField::filterEdited($data, 'cabin');
                    Cabin::update($data, "id=$diveCabinId");
                }
            }

            foreach ($cabin['occupancies'] as $occupancy) {
                $diveOccupancyId = Booker::booker2dive($occupancy['id'], Booker::TYPE_OCCUPANCY);
                $price = $occupancy['price']['current'];
                //$price = Currency::eur2cny((int)$occupancy['price']['current'] * 100);
                $data = [
                    'trip_id' => $oldTrip['id'],
                    'name_en' => $occupancy['title'],
                    'desc_en' => $occupancy['descr'],
                    //'cabin_id' => $occupancy['cabinId'],
                    'price' => $price,
                    'price_old' => (int)$occupancy['price']['old'],
                    'free_spaces' => (int)$occupancy['freeSpaces'],
                    //这他妈 返回的是 整个trip的freeSpaces,不能用啊哥 (booking方法抓这个字段了)  ago
                    //妈的 又正常了(booking方法先不跑了)  2018.8.23
                    'persons_max' => (int)$occupancy['persons']['max'],
                    'persons_current' => (int)$occupancy['persons']['current'],
                    'update_time' => date('Y-m-d H:i:s'),
                ];
                if ($syncCabin) $data['cabin_id'] = $diveCabinId;
                if ($occupancy['cabinId'] != $cabin['id']) {
                    var_dump($cabin['id']);
                    var_dump($occupancy['cabinId']);
                    exit;
                }
                $oldOccupancy = [];
                $diveOccupancyId && $oldOccupancy = (new Query())->select('id')->from('dive_occupancy')->where("id=$diveOccupancyId")->one();
                if (empty($oldOccupancy)) {
                    Occupancy::insert($data);
                    $diveOccupancyId = Yii::$app->db->getLastInsertID();
                    Booker::add($diveOccupancyId, $occupancy['id'], Booker::TYPE_OCCUPANCY);
                } else {
                    TableField::filterEdited($data, 'occupancy');
                    $oldPersonsCurrent = Occupancy::getScalar("id=$diveOccupancyId", 'persons_current');
                    if ($oldPersonsCurrent != $data['persons_current']) {
                        echo "Occupancy $diveOccupancyId,{$occupancy['id']} PersonsCurrent Old:$oldPersonsCurrent|New:{$data['persons_current']}\n";
                    }
                    Occupancy::update($data,"id=$diveOccupancyId");
                }
            }
        }
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " end\n";
    }

    public function products_revert() {
        Yii::$app->db->createCommand("truncate dive_tour")->execute();
        Yii::$app->db->createCommand("delete from dive_booker_map where type='tour'")->execute();
        $lastId = (new Query())->select('id')->from('dive_booker_map')->orderBy('id desc')->scalar();
        $sql = "alter table dive_booker_map auto_increment=$lastId";
        Yii::$app->db->createCommand($sql)->execute();
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__;
    }

    public function products() {
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " start\n";
        $offset = 0;
        while (true) {
            $list = Boat::getList('', 'id', 'id', $offset, 20);
            if ($this->singleId) $list = Boat::getList('id=' . $this->singleId);
            if (empty($list)) {
                echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " done\n";
                break;
            }
            foreach ($list as $k => $v) {
                $bookerBoatId = Booker::dive2booker($v['id'], 'boat');
                try {
                    $r = $this->guzzleClient->request('GET', self::HOST . sprintf(self::API['products'], $bookerBoatId) . '?token=' . DiveBooker::getToken(), [
                        'timeout' => 60,
                    ]);
                } catch (RequestException $e) {
                    echo "RequestException|" . $e->getCode() . "|" . $e->getMessage() . "\n";
                    if ($e->getResponse() &&  in_array($e->getResponse()->getStatusCode(), [400,404])) {
                        Boat::update(['booker_sync' => Booker::SYNC_404], "id={$v['id']}");
                    } elseif (strpos($e->getMessage(), 'cURL error 52') !== false) {

                    } else {
                        throw $e;
                    }
                    continue;
                }

                $res = json_decode($r->getBody(), true);
                if (empty($res['status']) || $res['status'] != 1) {
                    echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " fail:$r\n";
                    break 2;
                }
                foreach ($res['results']['toursInfo'] as $bookerTourId => $tour) {
                    $diveTourId = Booker::booker2dive($tour['id'], Booker::TYPE_TOUR);
                    $oldTour = [];
                    $diveTourId && $oldTour = (new Query())->select('id')->from('dive_tour')->where("id=$diveTourId")->one();
                    $dives = isset($tour['props']['dives']['text']) ? explode(' ', $tour['props']['dives']['text'])[0] : 0;
                    $data = [
                        'name_en' => $tour['name'],
                        'desc_en' => $tour['descr'],
                        'images' => json_encode($tour['images']),
                        'dives' => $dives,
                        'currency_symbol' => $tour['currencySymbol'],
                        'sertification' => $tour['props']['sertification']['text'] ?? '',
                        'arrival_location' => $tour['transferSection']['arrival']['text']['Transfer to'] ?? 0,
                        'departure_location' => $tour['transferSection']['departure']['text']['Transfer from'] ?? 0,
                        'check_in' => $tour['transferSection']['checkIn']['text'] ?? '',
                        'check_out' => $tour['transferSection']['checkOut']['text'] ?? '',
                        'update_time' => date('Y-m-d H:i:s'),
                    ];
                    if (empty($oldTour)) {
                        echo "booker_tour_id: {$tour['id']}\n";
                        Tour::insert($data);
                        $diveTourId = Yii::$app->db->getLastInsertID();
                        Booker::add($diveTourId, $tour['id'], Booker::TYPE_TOUR);
                        $this->translateTour([$diveTourId]);
                    } else {
                        Tour::update($data, "id=$diveTourId");
                    }

                    //                    Yii::$app->db->createCommand()->update('dive_trip', ['tour_id' => $diveTourId], "id={$oldTrip['id']}")->execute();
                    //                    Yii::$app->db->createCommand()->insert('dive_trip_tour', [
                    //                        'trip_id' => $oldTrip['id'],
                    //                        'tour_id' => $diveTourId
                    //                    ])->execute();

                    $this->tour($diveTourId, $bookerBoatId, $bookerTourId);
                }
                foreach ($res['results']['trips'] as $bookerTripId => $trip) {
                    $diveTripId = Booker::booker2dive($bookerTripId, Booker::TYPE_TRIP);
                    if ($diveTripId) {
                        $diveTourId = Booker::booker2dive($trip['tourId'], Booker::TYPE_TOUR);
                        if ($diveTourId) {
                            echo "diveTripId:$diveTripId|diveTourId,$diveTourId\n";
                            $oldFreeSpaces = Trip::getScalar("id=$diveTripId", 'free_spaces');
                            if (!isset($trip['sumFreeSpaces'])) {
                                var_export($trip);
                                continue;
                            }
                            if ($oldFreeSpaces != $trip['sumFreeSpaces']) {
                                echo "Trip $diveTripId-$bookerTripId FreeSpaces $oldFreeSpaces -> {$trip['sumFreeSpaces']}\n";
                                Trip::update([
                                    'tour_id' => $diveTourId,
                                    'rate_id' => (int)$trip['rateId'],
                                    //todo 未知,可能要做映射
                                    'free_spaces' => (int)$trip['sumFreeSpaces'],
                                ], "id=$diveTripId");
                            }
                        }
                    }
                }
                $equipmentRental = [];
                if (isset($res['results']['additional']) && is_array($res['results']['additional'])) {
                    foreach ($res['results']['additional'] as $item) {
                        if ($item['category'] == 'Equipment Rental') {
                            $equipmentRental = $item['list'];
                            break;
                        }
                    }
                }
            }
            if ($this->singleId) break;
            $offset += 20;
        }
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " end\n";
    }

    public function tour($diveTourId, $bookerBoatId, $bookerTourId) {
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " start\n";
        try {
            $rsp = $this->guzzleClient->request('GET', self::HOST . sprintf(self::API['tour'], $bookerBoatId), [
                'query' => [
                    'token' => DiveBooker::getToken(),
                    'productid' => $bookerTourId
                ],
                'timeout'=>60,
            ]);
        } catch (RequestException $e) {
            if ($e->getResponse() && $e->getResponse()->getStatusCode() == '400') {
                echo "RequestException|" . $e->getCode() . "|" . $e->getMessage() . "\n";
            } elseif (strpos($e->getMessage(), 'cURL error 52') !== false) {
            } else {
                throw $e;
            }
            return;
        }
        if (empty((string)$rsp->getBody())) {
            echo "empty res booker_tour_id:$bookerTourId\n";
            return;
        }
        sleep(2);
        $r = json_decode($rsp->getBody(), true);
        if (empty($r['status']) || $r['status'] != 1) {
            echo "res fail booker_tour_id:$bookerTourId|" . $rsp->getBody() . "\n";
            return;
        }
        foreach ($r['results']['toursInfo'] as $id => $tour) {
            $dives = isset($tour['props']['dives']['text']) ? explode(' ', $tour['props']['dives']['text'])[0] : 0;
            $data = [
                'name_en' => $tour['name'],
                'desc_en' => $tour['descr'],
                'images' => json_encode($tour['images']),
                'duration' => $tour['duration'],
                'nights' => $tour['nights'],
                'dives' => $dives,
                'experience' => $tour['minExperience'],
                'programm' => $tour['programm']['text'],
                'currency_symbol' => $tour['currencySymbol'],
                'update_time' => date('Y-m-d H:i:s'),
            ];
            foreach ($tour['additionalInfo']['columns'] as $column) {
                if ($column['type'] == 'included') {
                    $data['included'] = $column['text'];
                } elseif ($column['type'] == 'notincluded') {
                    $data['notincluded'] = $column['text'];
                } elseif ($column['type'] == 'extra') {
                    $data['extra'] = $column['text'];
                }
            }
            if (isset($tour['transferSection']['arrivalPort']['text'])) $data['arrival_port'] = $tour['transferSection']['arrivalPort']['text'];
            if (isset($tour['transferSection']['departurePort']['text'])) $data['departure_port'] = $tour['transferSection']['departurePort']['text'];
            if (isset($tour['tripRoute'])) $data['trip_route'] = json_encode($tour['tripRoute']);
            if (isset($tour['mapData'])) $data['map_data'] = json_encode($tour['mapData']);
            Tour::update($data, "id=$diveTourId");
        }
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " end\n";
    }

    /**
     * rate plan related Products 和钱相关的产品
     */
    public function product() { }

    /**
     * 新增tour时，自动翻译一些内容
     * @param array $ids
     */
    private function translateTour($ids = []) {
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " start\n";
        foreach ($ids as $id) {
            $item = Tour::getOne("id=$id");
            $data = [];
            if ($v = Tour::getScalar("name_en=:name_en", 'name_cn', [':name_en' => $item['name_en']])) $data['name_cn'] = $v;
            if ($v = Tour::getScalar("desc_en=:desc_en", 'desc_cn', [':desc_en' => $item['desc_en']])) $data['desc_cn'] = $v;
            if ($v = Tour::getScalar("included=:included_en", 'included_cn', [':included_en' => $item['included']])) $data['included_cn'] = $v;
            if ($v = Tour::getScalar("notincluded=:notincluded_en", 'notincluded_cn', [':notincluded_en' => $item['notincluded']])) $data['notincluded_cn'] = $v;
            if ($v = Tour::getScalar("departure_location=:departure_en", 'departure_cn', [':departure_en' => $item['departure_location']])) $data['departure_cn'] = $v;
            if ($v = Tour::getScalar("arrival_location=:arrival_en", 'arrival_cn', [':arrival_en' => $item['arrival_location']])) $data['arrival_cn'] = $v;

            if ($data) Tour::update($data, "id=$id");
        }
        echo date('Y/m/d H:i:s') . '|' . __FUNCTION__ . " end\n";
    }

    public function actionToken() {
        DiveBooker::updateToken();
        echo __METHOD__ . " " . DiveBooker::getToken() . "\n";
    }

    public function actionTestDiveOrder() {
        DiveBooker::makeOrder(1093, 6821, 20);
    }

    public function alarm($msg, $type) {
        if (isset($this->alarmType[$type]) && $this->alarmType[$type] - time() <= self::ALARM_TYPE_INTERVAL) return;
        $this->alarmType[$type] = time();
        if (isset($this->alarmMsg[$msg]) && $this->alarmMsg[$msg] - time() <= self::ALARM_MSG_INTERVAL) return;
        $this->alarmMsg[$msg] = time();
        Alarm::sys($msg, $type);
    }
}
